package com.lcy.struct.bTreePlus.disk.recovery.undo;

import com.lcy.struct.bTreePlus.disk.TestTree;
import com.lcy.struct.bTreePlus.disk.constants.BPlusPublicConstants;
import com.lcy.struct.bTreePlus.disk.file.FileUtil;
import com.lcy.struct.bTreePlus.disk.index.BTreeIndex;
import com.lcy.struct.bTreePlus.disk.item.TreeNode;
import com.lcy.struct.bTreePlus.disk.log.LogUtils;
import com.lcy.struct.bTreePlus.utils.BplusUtils;
import lombok.SneakyThrows;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author lucongyang
 * @date Created in  10:11
 * @description 逆向操作中心
 */
public class UndoCenter {
    private static final String UNDO_LOG = TestTree.root_url + "undo.log";
    private static ReentrantReadWriteLock undoEndPositionLOCK = new ReentrantReadWriteLock();
    public static final Lock UNDO_END_POSITION_WRITE_LOCK = undoEndPositionLOCK.writeLock();
    public static final Lock UNDO_END_POSITION_READ_LOCK = undoEndPositionLOCK.readLock();
    private Long undoEndPosition;

    private FileChannel undoChannel;

    Map<Long, Long> transactionIdUndoStartPositionMap = new ConcurrentHashMap<>();

    Map<Long, List<String>> transactionIdUndoStrListMap = new ConcurrentHashMap<>();


    public void doOnReboot() {
        LinkedHashMap<Long, List<String>> readUndo = ReadUndoLog.getInstance().readUndo();

        List<Long> list = DoUndo.getInstance().doUndo(readUndo);

        list.forEach(e -> CommitUndo.getInstance().commit(e));

        clearUndo(true);
    }

    /**
     * 提交
     */
    public void commit(long transactionId) {
        CommitUndo.getInstance().commit(transactionId);
    }

    /**
     * 回滚
     */
    public void doUndo(long transactionId, List<String> undoStrList) {
        boolean done = DoUndo.getInstance().doUndo(undoStrList);
        if (done) {
            LogUtils.log("rollback");
            System.out.println("rollback:" + undoStrList);
            commit(transactionId);
        }
    }

    /**
     * 获取事务数据
     */
    public List<String> getTransactionUndoStr(long transactionId) {
        return transactionIdUndoStrListMap.getOrDefault(transactionId, new ArrayList<>());
    }

    /**
     * 写undo
     */
    public void writeUndoStr(long transactionId, List<String> undoStrList) {
        if (undoStrList != null && !undoStrList.isEmpty()) {
            WriteUndoLog.getInstance().writeUndoStr(transactionId, undoStrList);
        }
    }

    /**
     * 生成Add undo日志
     *
     * @param index 主键
     */
    public String getAddUndo(BTreeIndex index) {
        return UndoAdd.getInstance().getUndo(index);
    }

    /**
     * 生成Update undo日志
     *
     * @param oldIndex 主键
     * @param treeNode 全新数据
     */
    public String getUpdateUndo(BTreeIndex oldIndex, TreeNode treeNode) {
        return UndoUpdate.getInstance().getUndo(oldIndex, treeNode);
    }

    /**
     * 生成del undo日志
     *
     * @param index 主键
     */
    public String getDelUndo(BTreeIndex index) {
        Comparable[] comparables = new Comparable[index.getIndexList().size()];
        index.getIndexList().toArray(comparables);
        return UndoDel.getInstance().getUndo(comparables);
    }

    public static UndoCenter getInstance() {
        return Singleton.INSTANCE;
    }

    private static class Singleton {
        private static final UndoCenter INSTANCE = new UndoCenter();
    }

    private UndoCenter() {
    }

    /**
     * 清空undo日志
     *
     * @param force 是否彻底清空，删文件再恢复
     */
    public void clearUndo(boolean force) {
        UNDO_END_POSITION_WRITE_LOCK.lock();
        try {
            long size = getUndoEndPosition() - BPlusPublicConstants.UNDO_END_POSITION - BPlusPublicConstants.HEAD_SIZE;
            modifyUndoEndPosition(BPlusPublicConstants.UNDO_END_POSITION + BPlusPublicConstants.HEAD_SIZE);
            if (force) {
                int n = 1000000;
                for (int i = 0; i * n < size; i++) {
                    byte[] bytes = new byte[(int) Math.min(size - i * n, n)];
                    BplusUtils.initByteArray(bytes);
                    FileUtil.getInstance().straightRewrite(getUndoChannel(), i * n + BPlusPublicConstants.UNDO_END_POSITION + BPlusPublicConstants.HEAD_SIZE,
                            bytes.length, true, bytes);
                }
            }
        } finally {
            UNDO_END_POSITION_WRITE_LOCK.unlock();
        }
    }

    /**
     * 写wndPosition
     */
    void allocate(long size) {
        UNDO_END_POSITION_WRITE_LOCK.lock();
        try {
            long exceptEndPosition = getUndoEndPosition() + size;
            modifyUndoEndPosition(exceptEndPosition);
            this.undoEndPosition = exceptEndPosition;
        } finally {
            UNDO_END_POSITION_WRITE_LOCK.unlock();
        }
    }

    /**
     * 获取通道
     */
    FileChannel getUndoChannel() {
        if (this.undoChannel != null) {
            return this.undoChannel;
        }
        RandomAccessFile randomAccessFile = getUndoAccessFile();
        this.undoChannel = randomAccessFile.getChannel();
        return this.undoChannel;
    }

    long getUndoEndPosition() {
        UNDO_END_POSITION_READ_LOCK.lock();
        try {
            if (this.undoEndPosition != null) {
                return this.undoEndPosition;
            }
            byte[] endPositionByte = FileUtil.getInstance().read(BPlusPublicConstants.UNDO_END_POSITION, BPlusPublicConstants.HEAD_SIZE, getUndoChannel());
            String s = new String(endPositionByte).trim();
            this.undoEndPosition = s.isEmpty() ? BPlusPublicConstants.UNDO_END_POSITION + BPlusPublicConstants.HEAD_SIZE : Long.parseLong(s);
            return this.undoEndPosition;
        } finally {
            UNDO_END_POSITION_READ_LOCK.unlock();
        }
    }

    /**
     * 修改指针
     */
    @SneakyThrows
    private void modifyUndoEndPosition(long exceptEndPosition) {
        UNDO_END_POSITION_WRITE_LOCK.lock();
        try {
            FileUtil.getInstance().straightRewrite(getUndoChannel(), BPlusPublicConstants.UNDO_END_POSITION,
                    BPlusPublicConstants.HEAD_SIZE, true, (exceptEndPosition + "").getBytes());
            this.undoEndPosition = exceptEndPosition;
        } finally {
            UNDO_END_POSITION_WRITE_LOCK.unlock();
        }
    }

    private RandomAccessFile getUndoAccessFile() {
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(UNDO_LOG, "rw");
        } catch (FileNotFoundException e) {
            LogUtils.log("undo文件：{}不存在，正在初始化", UNDO_LOG);
            createUndo();
            return getUndoAccessFile();
        }
        return randomAccessFile;
    }

    /**
     * 初始化undo
     * 会修改endPosition值
     */
    private void createUndo() {
        File file = new File(UNDO_LOG);
        UNDO_END_POSITION_WRITE_LOCK.lock();
        try {
            this.undoEndPosition = BPlusPublicConstants.UNDO_END_POSITION + BPlusPublicConstants.HEAD_SIZE;
        } finally {
            UNDO_END_POSITION_WRITE_LOCK.unlock();
        }
    }
}
